<!DOCTYPE html>
<html lang="zh-CN">





<head>
  <meta charset="UTF-8">
  <link rel="apple-touch-icon" sizes="76x76" href="/blog/img/favicon.png">
  <link rel="icon" type="image/png" href="/blog/img/favicon.png">
  <meta name="viewport"
        content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, shrink-to-fit=no">
  <meta http-equiv="x-ua-compatible" content="ie=edge">
  
    <meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests">
  
  <meta name="theme-color" content="#35495e">
  <meta name="description" content="小菜头的博客，主要记录学习笔记，包含：html,css,javascript,jQuery,vue等">
  <meta name="author" content="A-cai">
  <meta name="keywords" content="html,css,javascript,jQuery,vue">
  <title>什么是AST - A-cai的blog</title>

  <link  rel="stylesheet" href="https://cdn.staticfile.org/twitter-bootstrap/4.4.1/css/bootstrap.min.css" />


  <link  rel="stylesheet" href="https://cdn.staticfile.org/github-markdown-css/4.0.0/github-markdown.min.css" />
  <link  rel="stylesheet" href="/blog/lib/hint/hint.min.css" />

  
    <link  rel="stylesheet" href="https://cdn.staticfile.org/highlight.js/10.0.0/styles/atom-one-dark.min.css" />
  

  


<!-- 主题依赖的图标库，不要自行修改 -->

<link rel="stylesheet" href="//at.alicdn.com/t/font_1749284_yg9cfy8wd6.css">



<link rel="stylesheet" href="//at.alicdn.com/t/font_1736178_pjno9b9zyxs.css">


<link  rel="stylesheet" href="/blog/css/main.css" />

<!-- 自定义样式保持在最底部 -->


  <script  src="/blog/js/utils.js" ></script>
<meta name="generator" content="Hexo 4.2.0"></head>


<body>
  <header style="height: 70vh;">
    <nav id="navbar" class="navbar fixed-top  navbar-expand-lg navbar-dark scrolling-navbar">
  <div class="container">
    <a class="navbar-brand"
       href="/blog/">&nbsp;<strong>A-cai的学习笔记</strong>&nbsp;</a>

    <button id="navbar-toggler-btn" class="navbar-toggler" type="button" data-toggle="collapse"
            data-target="#navbarSupportedContent"
            aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
      <div class="animated-icon"><span></span><span></span><span></span></div>
    </button>

    <!-- Collapsible content -->
    <div class="collapse navbar-collapse" id="navbarSupportedContent">
      <ul class="navbar-nav ml-auto text-center">
        
          
          
          
          
            <li class="nav-item">
              <a class="nav-link" href="/blog/">
                <i class="iconfont icon-home-fill"></i>
                首页
              </a>
            </li>
          
        
          
          
          
          
            <li class="nav-item">
              <a class="nav-link" href="/blog/archives/">
                <i class="iconfont icon-archive-fill"></i>
                归档
              </a>
            </li>
          
        
          
          
          
          
            <li class="nav-item">
              <a class="nav-link" href="/blog/categories/">
                <i class="iconfont icon-category-fill"></i>
                分类
              </a>
            </li>
          
        
          
          
          
          
            <li class="nav-item">
              <a class="nav-link" href="/blog/tags/">
                <i class="iconfont icon-tags-fill"></i>
                标签
              </a>
            </li>
          
        
          
          
          
          
            <li class="nav-item">
              <a class="nav-link" href="/blog/about/">
                <i class="iconfont icon-user-fill"></i>
                关于
              </a>
            </li>
          
        
        
          <li class="nav-item" id="search-btn">
            <a class="nav-link" data-toggle="modal" data-target="#modalSearch">&nbsp;&nbsp;<i
                class="iconfont icon-search"></i>&nbsp;&nbsp;</a>
          </li>
        
      </ul>
    </div>
  </div>
</nav>

    <div class="banner intro-2" id="background" parallax=true
         style="background: url('/blog/img/default.png') no-repeat center center;
           background-size: cover;">
      <div class="full-bg-img">
        <div class="mask flex-center" style="background-color: rgba(0, 0, 0, 0.3)">
          <div class="container text-center white-text fade-in-up">
            <span class="h2" id="subtitle">
              
            </span>

            
              
  <div class="mt-3 post-meta">
    <i class="iconfont icon-date-fill" aria-hidden="true"></i>
    <time datetime="2020-06-26 14:05">
      2020年6月26日 下午
    </time>
  </div>


<div class="mt-1">
  
    
    <span class="post-meta mr-2">
      <i class="iconfont icon-chart"></i>
      4.6k 字
    </span>
  

  
    
    <span class="post-meta mr-2">
      <i class="iconfont icon-clock-fill"></i>
      
      
      58
       分钟
    </span>
  

  
  
    
      <!-- 不蒜子统计文章PV -->
      <span id="busuanzi_container_page_pv" style="display: none">
        <i class="iconfont icon-eye" aria-hidden="true"></i>
        <span id="busuanzi_value_page_pv"></span> 次
      </span>
    
  
</div>

            
          </div>

          
        </div>
      </div>
    </div>
  </header>

  <main>
    
      

<div class="container-fluid">
  <div class="row">
    <div class="d-none d-lg-block col-lg-2"></div>
    <div class="col-lg-8 nopadding-md">
      <div class="container nopadding-md" id="board-ctn">
        <div class="py-5" id="board">
          <div class="post-content mx-auto" id="post">
            
              <p class="note note-info">
                
                  本文最后更新于：2020年3月21日 中午
                
              </p>
            
            <article class="markdown-body">
              <h1 id="AST抽象语法树——最基础的javascript重点知识，99-的人根本不了解"><a href="#AST抽象语法树——最基础的javascript重点知识，99-的人根本不了解" class="headerlink" title="AST抽象语法树——最基础的javascript重点知识，99%的人根本不了解"></a>AST抽象语法树——最基础的javascript重点知识，99%的人根本不了解</h1><blockquote>
<p>抽象语法树（AST），是一个非常基础而重要的知识点，但国内的文档却几乎一片空白。</p>
<p>本文将带大家从底层了解AST,并且通过发布一个小型前端工具，来带大家了解AST的强大功能</p>
</blockquote>
<p>Javascript就像一台精妙运作的机器，我们可以用它来完成一切天马行空的构思。</p>
<p>我们对javascript生态了如指掌，却常忽视javascript本身。这台机器，究竟是哪些零部件在支持着它运行？</p>
<p>AST在日常业务中也许很难涉及到，但当你不止于想做一个工程师，而想做工程师的工程师，写出vue、react之类的大型框架，或类似webpack、vue-cli前端自动化的工具，或者有批量修改源码的工程需求，那你必须懂得AST。AST的能力十分强大，且能帮你真正吃透javascript的语言精髓。</p>
<p>事实上，在javascript世界中，你可以认为抽象语法树(AST)是最底层。 再往下，就是关于转换和编译的“黑魔法”领域了。</p>
<h2 id="人生第一次拆解Javascript"><a href="#人生第一次拆解Javascript" class="headerlink" title="人生第一次拆解Javascript"></a>人生第一次拆解Javascript</h2><p>小时候，当我们拿到一个螺丝刀和一台机器，人生中最令人怀念的梦幻时刻便开始了：</p>
<p>我们把机器，拆成一个一个小零件，一个个齿轮与螺钉，用巧妙的机械原理衔接在一起…</p>
<p>当我们把它重新照不同的方式组装起来，这时，机器重新又跑动了起来——世界在你眼中如获新生。</p>
<p><img src="https://segmentfault.com/img/remote/1460000016231515?w=658&h=658" srcset="/blog/img/loading.gif" alt="image"></p>
<p>通过抽象语法树解析，我们可以像童年时拆解玩具一样，透视Javascript这台机器的运转，并且重新按着你的意愿来组装。</p>
<p><strong>现在，我们拆解一个简单的add函数</strong></p>
<div class="hljs"><pre><code class="hljs ada"><span class="hljs-keyword">function</span> <span class="hljs-title">add</span>(a, b) &#123;
    <span class="hljs-keyword">return</span> <span class="hljs-type">a</span> + b
&#125;</code></pre></div>

<p>首先，我们拿到的这个语法块，是一个FunctionDeclaration(函数定义)对象。</p>
<p>用力拆开，它成了三块：</p>
<ul>
<li>一个id，就是它的名字，即add</li>
<li>两个params，就是它的参数，即[a, b]</li>
<li>一块body，也就是大括号内的一堆东西</li>
</ul>
<p>add没办法继续拆下去了，它是一个最基础Identifier（标志）对象，用来作为函数的唯一标志，就像人的姓名一样。</p>
<div class="hljs"><pre><code class="hljs css">&#123;
    <span class="hljs-attribute">name</span>: <span class="hljs-string">'add'</span>
    type: <span class="hljs-string">'identifier'</span>
    ...
&#125;</code></pre></div>

<p>params继续拆下去，其实是两个Identifier组成的数组。之后也没办法拆下去了。</p>
<div class="hljs"><pre><code class="hljs scheme">[
    &#123;
        name: <span class="hljs-symbol">'a</span>'
        type: <span class="hljs-symbol">'identifier</span>'
        ...
    &#125;,
    &#123;
        name: <span class="hljs-symbol">'b</span>'
        type: <span class="hljs-symbol">'identifier</span>'
        ...
    &#125;
]</code></pre></div>

<p>接下来，我们继续拆开body<br>我们发现，body其实是一个BlockStatement（块状域）对象，用来表示是<code>{return a + b}</code></p>
<p>打开Blockstatement，里面藏着一个ReturnStatement（Return域）对象，用来表示<code>return a + b</code></p>
<p>继续打开ReturnStatement,里面是一个BinaryExpression(二项式)对象，用来表示<code>a + b</code></p>
<p>继续打开BinaryExpression，它成了三部分，<code>left</code>，<code>operator</code>，<code>right</code></p>
<ul>
<li><code>operator</code> 即<code>+</code></li>
<li><code>left</code> 里面装的，是Identifier对象 <code>a</code></li>
<li><code>right</code> 里面装的，是Identifer对象 <code>b</code></li>
</ul>
<p>就这样，我们把一个简单的add函数拆解完毕，用图表示就是</p>
<p><img src="https://segmentfault.com/img/remote/1460000016231998?w=1380&h=910" srcset="/blog/img/loading.gif" alt="image"></p>
<p>看！抽象语法树(Abstract Syntax Tree)，的确是一种标准的树结构。</p>
<p>那么，上面我们提到的Identifier、Blockstatement、ReturnStatement、BinaryExpression， 这一个个小部件的说明书去哪查？</p>
<p><strong>请查看 <a href="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey/Parser_API#Node_objects" target="_blank" rel="noopener">AST对象文档</a></strong></p>
<h3 id="送给你的AST螺丝刀：recast"><a href="#送给你的AST螺丝刀：recast" class="headerlink" title="送给你的AST螺丝刀：recast"></a>送给你的AST螺丝刀：recast</h3><p>输入命令：</p>
<div class="hljs"><pre><code class="hljs coffeescript"><span class="hljs-built_in">npm</span> i recast -S</code></pre></div>

<p>你即可获得一把操纵语法树的螺丝刀</p>
<p>接下来，你可以在任意js文件下操纵这把螺丝刀，我们新建一个parse.js示意：</p>
<p><strong>parse.js</strong></p>
<div class="hljs"><pre><code class="hljs stata"><span class="hljs-comment">// 给你一把"螺丝刀"——recast</span>
<span class="hljs-keyword">const</span> <span class="hljs-keyword">recast</span> = require(<span class="hljs-string">"recast"</span>);

<span class="hljs-comment">// 你的"机器"——一段代码</span>
<span class="hljs-comment">// 我们使用了很奇怪格式的代码，想测试是否能维持代码结构</span>
<span class="hljs-keyword">const</span> code =
  `
  function add(a, b) &#123;
    <span class="hljs-keyword">return</span> a +
      <span class="hljs-comment">// 有什么奇怪的东西混进来了</span>
      b
  &#125;
  `
<span class="hljs-comment">// 用螺丝刀解析机器</span>
<span class="hljs-keyword">const</span> ast = <span class="hljs-keyword">recast</span>.<span class="hljs-keyword">parse</span>(code);

<span class="hljs-comment">// ast可以处理很巨大的代码文件</span>
<span class="hljs-comment">// 但我们现在只需要代码块的第一个body，即add函数</span>
<span class="hljs-keyword">const</span> add  = ast.<span class="hljs-keyword">program</span>.body[0]

console.<span class="hljs-built_in">log</span>(add)</code></pre></div>

<p>输入<code>node parse.js</code>你可以查看到add函数的结构，与之前所述一致，通过<a href="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey/Parser_API#Node_objects" target="_blank" rel="noopener">AST对象文档</a>可查到它的具体属性：</p>
<div class="hljs"><pre><code class="hljs lasso">FunctionDeclaration&#123;
    <span class="hljs-keyword">type</span>: <span class="hljs-string">'FunctionDeclaration'</span>,
    id: <span class="hljs-params">...</span>
    <span class="hljs-keyword">params</span>: <span class="hljs-params">...</span>
    body: <span class="hljs-params">...</span>
&#125;</code></pre></div>

<p>你也可以继续使用console.log透视它的更内层，如：</p>
<div class="hljs"><pre><code class="hljs css"><span class="hljs-selector-tag">console</span><span class="hljs-selector-class">.log</span>(<span class="hljs-selector-tag">add</span><span class="hljs-selector-class">.params</span><span class="hljs-selector-attr">[0]</span>)
<span class="hljs-selector-tag">console</span><span class="hljs-selector-class">.log</span>(<span class="hljs-selector-tag">add</span><span class="hljs-selector-class">.body</span><span class="hljs-selector-class">.body</span><span class="hljs-selector-attr">[0]</span><span class="hljs-selector-class">.argument</span><span class="hljs-selector-class">.left</span>)</code></pre></div>

<h2 id="recast-types-builders-制作模具"><a href="#recast-types-builders-制作模具" class="headerlink" title="recast.types.builders 制作模具"></a>recast.types.builders 制作模具</h2><p>一个机器，你只会拆开重装，不算本事。</p>
<p>拆开了，还能改装，才算上得了台面。</p>
<p>recast.types.builders里面提供了不少“模具”，让你可以轻松地拼接成新的机器。</p>
<p>最简单的例子，我们想把之前的<code>function add(a, b){...}</code>声明，改成匿名函数式声明<code>const add = function(a ,b){...}</code></p>
<p>如何改装？</p>
<p>第一步，我们创建一个VariableDeclaration变量声明对象，声明头为const， 内容为一个即将创建的VariableDeclarator对象。</p>
<p>第二步，创建一个VariableDeclarator，放置add.id在左边， 右边是将创建的FunctionDeclaration对象</p>
<p>第三步，我们创建一个FunctionDeclaration，如前所述的三个组件，id params body中，因为是匿名函数id设为空，params使用add.params，body使用add.body。</p>
<p>这样，就创建好了<code>const add = function(){}</code>的AST对象。</p>
<p>在之前的parse.js代码之后，加入以下代码</p>
<div class="hljs"><pre><code class="hljs processing"><span class="hljs-comment">// 引入变量声明，变量符号，函数声明三种“模具”</span>
<span class="hljs-keyword">const</span> &#123;variableDeclaration, variableDeclarator, functionExpression&#125; = recast.types.builders

<span class="hljs-comment">// 将准备好的组件置入模具，并组装回原来的ast对象。</span>
ast.program.body[<span class="hljs-number">0</span>] = variableDeclaration(<span class="hljs-string">"const"</span>, [
  variableDeclarator(<span class="hljs-built_in">add</span>.id, functionExpression(
    <span class="hljs-keyword">null</span>, <span class="hljs-comment">// Anonymize the function expression.</span>
    <span class="hljs-built_in">add</span>.params,
    <span class="hljs-built_in">add</span>.body
  ))
]);

<span class="hljs-comment">//将AST对象重新转回可以阅读的代码</span>
<span class="hljs-keyword">const</span> output = recast.<span class="hljs-built_in">print</span>(ast).code;

console.<span class="hljs-built_in">log</span>(output)</code></pre></div>

<p>可以看到，我们打印出了</p>
<div class="hljs"><pre><code class="hljs actionscript"><span class="hljs-keyword">const</span> add = <span class="hljs-function"><span class="hljs-keyword">function</span><span class="hljs-params">(a, b)</span> </span>&#123;
  <span class="hljs-keyword">return</span> a +
    <span class="hljs-comment">// 有什么奇怪的东西混进来了</span>
    b
&#125;;</code></pre></div>

<p>最后一行</p>
<div class="hljs"><pre><code class="hljs stata"><span class="hljs-keyword">const</span> output = <span class="hljs-keyword">recast</span>.<span class="hljs-keyword">print</span>(ast).code;</code></pre></div>

<p>其实是recast.parse的逆向过程，具体公式为</p>
<div class="hljs"><pre><code class="hljs stata"><span class="hljs-keyword">recast</span>.<span class="hljs-keyword">print</span>(<span class="hljs-keyword">recast</span>.<span class="hljs-keyword">parse</span>(source)).code === source</code></pre></div>

<p>打印出来还保留着“原装”的函数内容，连注释都没有变。</p>
<p>我们其实也可以打印出美化格式的代码段：</p>
<div class="hljs"><pre><code class="hljs reasonml">const output = recast.pretty<span class="hljs-constructor">Print(<span class="hljs-params">ast</span>, &#123; <span class="hljs-params">tabWidth</span>: 2 &#125;)</span>.code</code></pre></div>

<p>输出为</p>
<div class="hljs"><pre><code class="hljs oxygene"><span class="hljs-keyword">const</span> <span class="hljs-keyword">add</span> = <span class="hljs-function"><span class="hljs-keyword">function</span><span class="hljs-params">(a, b)</span> <span class="hljs-comment">&#123;</span></span>
<span class="hljs-function"><span class="hljs-comment">  return a + b;</span></span>
<span class="hljs-function"><span class="hljs-comment">&#125;</span>;</span></code></pre></div>

<blockquote>
<p>现在，你是不是已经产生了“我可以通过AST树生成任何js代码”的幻觉？</p>
<p>我郑重告诉你，这不是幻觉。</p>
</blockquote>
<h2 id="实战进阶：命令行修改js文件"><a href="#实战进阶：命令行修改js文件" class="headerlink" title="实战进阶：命令行修改js文件"></a>实战进阶：命令行修改js文件</h2><p>除了parse/print/builder以外，Recast的三项主要功能：</p>
<ul>
<li>run: 通过命令行读取js文件，并转化成ast以供处理。</li>
<li>tnt： 通过assert()和check()，可以验证ast对象的类型。</li>
<li>visit: 遍历ast树，获取有效的AST对象并进行更改。</li>
</ul>
<p>我们通过一个系列小务来学习全部的recast工具库：</p>
<p>创建一个用来示例文件，假设是demo.js</p>
<p><strong>demo.js</strong></p>
<div class="hljs"><pre><code class="hljs lua"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">add</span><span class="hljs-params">(a, b)</span></span> &#123;
  <span class="hljs-keyword">return</span> a + b
&#125;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">sub</span><span class="hljs-params">(a, b)</span></span> &#123;
  <span class="hljs-keyword">return</span> a - b
&#125;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">commonDivision</span><span class="hljs-params">(a, b)</span></span> &#123;
  <span class="hljs-keyword">while</span> (b !== <span class="hljs-number">0</span>) &#123;
    <span class="hljs-keyword">if</span> (a &gt; b) &#123;
      a = <span class="hljs-built_in">sub</span>(a, b)
    &#125; <span class="hljs-keyword">else</span> &#123;
      b = <span class="hljs-built_in">sub</span>(b, a)
    &#125;
  &#125;
  <span class="hljs-keyword">return</span> a
&#125;</code></pre></div>

<h3 id="recast-run-——-命令行文件读取"><a href="#recast-run-——-命令行文件读取" class="headerlink" title="recast.run —— 命令行文件读取"></a>recast.run —— 命令行文件读取</h3><p>新建一个名为<code>read.js</code>的文件，写入<br><strong>read.js</strong></p>
<div class="hljs"><pre><code class="hljs actionscript">recast.run( <span class="hljs-function"><span class="hljs-keyword">function</span><span class="hljs-params">(ast, printSource)</span></span>&#123;
    printSource(ast)
&#125;)</code></pre></div>

<p>命令行输入</p>
<div class="hljs"><pre><code class="hljs crmsh"><span class="hljs-keyword">node</span> <span class="hljs-title">read</span> demo.js</code></pre></div>

<p>我们查以看到js文件内容打印在了控制台上。</p>
<p>我们可以知道，<code>node read</code>可以读取<code>demo.js</code>文件，并将demo.js内容转化为ast对象。</p>
<p>同时它还提供了一个<code>printSource</code>函数，随时可以将ast的内容转换回源码，以方便调试。</p>
<h3 id="recast-visit-——-AST节点遍历"><a href="#recast-visit-——-AST节点遍历" class="headerlink" title="recast.visit —— AST节点遍历"></a>recast.visit —— AST节点遍历</h3><p><strong>read.js</strong></p>
<div class="hljs"><pre><code class="hljs javascript"><span class="hljs-meta">#!/usr/bin/env node</span>
<span class="hljs-keyword">const</span> recast  = <span class="hljs-built_in">require</span>(<span class="hljs-string">'recast'</span>)

recast.run(<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">ast, printSource</span>) </span>&#123;
  recast.visit(ast, &#123;
      visitExpressionStatement: <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">&#123;node&#125;</span>) </span>&#123;
        <span class="hljs-built_in">console</span>.log(node)
        <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>
      &#125;
    &#125;);
&#125;);</code></pre></div>

<p>recast.visit将AST对象内的节点进行逐个遍历。</p>
<p><strong>注意</strong></p>
<ul>
<li>你想操作函数声明，就使用visitFunctionDelaration遍历，想操作赋值表达式，就使用visitExpressionStatement。 只要在 <a href="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey/Parser_API#Node_objects" target="_blank" rel="noopener">AST对象文档</a>中定义的对象，在前面加visit，即可遍历。</li>
<li>通过node可以取到AST对象</li>
<li>每个遍历函数后必须加上return false，或者选择以下写法，否则报错：</li>
</ul>
<div class="hljs"><pre><code class="hljs crmsh"><span class="hljs-comment">#!/usr/bin/env node</span>
const recast  = require('recast')

recast.run(function(ast, printSource) &#123;
  recast.visit(ast, &#123;
      visitExpressionStatement: function(path) &#123;
        const <span class="hljs-keyword">node</span> <span class="hljs-title">= path</span>.<span class="hljs-keyword">node</span><span class="hljs-title"></span>
<span class="hljs-title">        printSource</span>(<span class="hljs-keyword">node</span><span class="hljs-title">)</span>
<span class="hljs-title">        this</span>.traverse(path)
      &#125;
    &#125;)
&#125;);</code></pre></div>

<p>调试时，如果你想输出AST对象，可以<code>console.log(node)</code></p>
<p>如果你想输出AST对象对应的源码，可以<code>printSource(node)</code></p>
<p>命令行输入<code>node read demo.js</code>进行测试。</p>
<blockquote>
<p><code>#!/usr/bin/env node</code> 在所有使用<code>recast.run()</code>的文件顶部都需要加入这一行，它的意义我们最后再讨论。</p>
</blockquote>
<h3 id="TNT-——-判断AST对象类型"><a href="#TNT-——-判断AST对象类型" class="headerlink" title="TNT —— 判断AST对象类型"></a>TNT —— 判断AST对象类型</h3><p>TNT，即recast.types.namedTypes，就像它的名字一样火爆，它用来判断AST对象是否为指定的类型。</p>
<p>TNT.Node.assert()，就像在机器里埋好的炸药，当机器不能完好运转时（类型不匹配），就炸毁机器(报错退出)</p>
<p>TNT.Node.check()，则可以判断类型是否一致，并输出False和True</p>
<p>上述Node可以替换成任意AST对象，例如TNT.ExpressionStatement.check(),TNT.FunctionDeclaration.assert()</p>
<p><strong>read.js</strong></p>
<div class="hljs"><pre><code class="hljs javascript"><span class="hljs-meta">#!/usr/bin/env node</span>
<span class="hljs-keyword">const</span> recast = <span class="hljs-built_in">require</span>(<span class="hljs-string">"recast"</span>);
<span class="hljs-keyword">const</span> TNT = recast.types.namedTypes

recast.run(<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">ast, printSource</span>) </span>&#123;
  recast.visit(ast, &#123;
      visitExpressionStatement: <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">path</span>) </span>&#123;
        <span class="hljs-keyword">const</span> node = path.value
        <span class="hljs-comment">// 判断是否为ExpressionStatement，正确则输出一行字。</span>
        <span class="hljs-keyword">if</span>(TNT.ExpressionStatement.check(node))&#123;
          <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'这是一个ExpressionStatement'</span>)
        &#125;
        <span class="hljs-keyword">this</span>.traverse(path);
      &#125;
    &#125;);
&#125;);</code></pre></div>

<p><strong>read.js</strong></p>
<div class="hljs"><pre><code class="hljs crmsh"><span class="hljs-comment">#!/usr/bin/env node</span>
const recast = require(<span class="hljs-string">"recast"</span>);
const TNT = recast.types.namedTypes

recast.run(function(ast, printSource) &#123;
  recast.visit(ast, &#123;
      visitExpressionStatement: function(path) &#123;
        const <span class="hljs-keyword">node</span> <span class="hljs-title">= path</span>.<span class="hljs-keyword">node</span><span class="hljs-title"></span>
<span class="hljs-title">        // 判断是否为ExpressionStatement</span>，正确不输出，错误则全局报错
        TNT.ExpressionStatement.assert(<span class="hljs-keyword">node</span><span class="hljs-title">)</span>
<span class="hljs-title">        this</span>.traverse(path);
      &#125;
    &#125;);
&#125;);</code></pre></div>

<p>命令行输入<code>node read demo.js</code>进行测试。</p>
<h3 id="实战：用AST修改源码，导出全部方法"><a href="#实战：用AST修改源码，导出全部方法" class="headerlink" title="实战：用AST修改源码，导出全部方法"></a>实战：用AST修改源码，导出全部方法</h3><p>exportific.js</p>
<p>现在，我们想让这个文件中的函数改写成能够全部导出的形式，例如</p>
<div class="hljs"><pre><code class="hljs ada"><span class="hljs-keyword">function</span> <span class="hljs-title">add</span> (a, b) &#123;
    <span class="hljs-keyword">return</span> <span class="hljs-type">a</span> + b
&#125;</code></pre></div>

<p>想改变为</p>
<div class="hljs"><pre><code class="hljs coffeescript">exports.add = <span class="hljs-function"><span class="hljs-params">(a, b)</span> =&gt;</span> &#123;
  <span class="hljs-keyword">return</span> a + b
&#125;</code></pre></div>

<p>除了使用fs.read读取文件、正则匹配替换文本、fs.write写入文件这种笨拙的方式外，我们可以<strong>用AST优雅地解决问题</strong>。</p>
<p>查询<a href="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey/Parser_API#Node_objects" target="_blank" rel="noopener">AST对象文档</a></p>
<h4 id="首先，我们先用builders凭空实现一个键头函数"><a href="#首先，我们先用builders凭空实现一个键头函数" class="headerlink" title="首先，我们先用builders凭空实现一个键头函数"></a>首先，我们先用builders凭空实现一个键头函数</h4><p><strong>exportific.js</strong></p>
<div class="hljs"><pre><code class="hljs sas">#!/usr/bin/env node
const recast = require(<span class="hljs-string">"recast"</span>);
const &#123;
  identifier:id,
  expressionStatement,
  memberExpression,
  assignmentExpression,
  arrowFunctionExpression,
  blockStatement
&#125; = recast.types.builders

recast.r<span class="hljs-meta">un(</span>functi<span class="hljs-meta">on(</span>ast, printSource) &#123;
  // 一个块级域 &#123;&#125;
  console<span class="hljs-meta">.log(</span><span class="hljs-string">'\n\nstep1:'</span>)
  printSource(blockStatement([]))

  // 一个键头函数 ()=&gt;&#123;&#125;
  console<span class="hljs-meta">.log(</span><span class="hljs-string">'\n\nstep2:'</span>)
  printSource(arrowFunctionExpressi<span class="hljs-meta">on(</span>[],blockStatement([])))

  // <span class="hljs-meta">add</span>赋值为键头函数  <span class="hljs-meta">add</span> = ()=&gt;&#123;&#125;
  console<span class="hljs-meta">.log(</span><span class="hljs-string">'\n\nstep3:'</span>)
  printSource(assignmentExpressi<span class="hljs-meta">on(</span><span class="hljs-string">'='</span>,id(<span class="hljs-string">'add'</span>),arrowFunctionExpressi<span class="hljs-meta">on(</span>[],blockStatement([]))))

  // exports.<span class="hljs-meta">add</span>赋值为键头函数  exports.<span class="hljs-meta">add</span> = ()=&gt;&#123;&#125;
  console<span class="hljs-meta">.log(</span><span class="hljs-string">'\n\nstep4:'</span>)
  printSource(expressionStatement(assignmentExpressi<span class="hljs-meta">on(</span><span class="hljs-string">'='</span>,memberExpressi<span class="hljs-meta">on(</span>id(<span class="hljs-string">'exports'</span>),id(<span class="hljs-string">'add'</span>)),
    arrowFunctionExpressi<span class="hljs-meta">on(</span>[],blockStatement([])))))
&#125;);</code></pre></div>

<p>上面写了我们一步一步推断出<code>exports.add = ()=&gt;{}</code>的过程，从而得到具体的AST结构体。</p>
<p>使用<code>node exportific demo.js</code>运行可查看结果。</p>
<p>接下来，只需要在获得的最终的表达式中，把id(‘add’)替换成遍历得到的函数名，把参数替换成遍历得到的函数参数，把blockStatement([])替换为遍历得到的函数块级作用域，就成功地改写了所有函数！</p>
<p>另外，我们需要注意，在commonDivision函数内，引用了sub函数，应改写成exports.sub</p>
<p><strong>exportific.js</strong></p>
<div class="hljs"><pre><code class="hljs crmsh"><span class="hljs-comment">#!/usr/bin/env node</span>
const recast = require(<span class="hljs-string">"recast"</span>);
const &#123;
  identifier: id,
  expressionStatement,
  memberExpression,
  assignmentExpression,
  arrowFunctionExpression
&#125; = recast.types.builders

recast.run(function (ast, printSource) &#123;
  // 用来保存遍历到的全部函数名
  let funcIds = []
  recast.types.visit(ast, &#123;
    // 遍历所有的函数定义
    visitFunctionDeclaration(path) &#123;
      //获取遍历到的函数名、参数、块级域
      const <span class="hljs-keyword">node</span> <span class="hljs-title">= path</span>.<span class="hljs-keyword">node</span><span class="hljs-title"></span>
<span class="hljs-title">      const</span> funcName = <span class="hljs-keyword">node</span>.<span class="hljs-title">id</span>
      const <span class="hljs-keyword">params</span> = <span class="hljs-keyword">node</span>.<span class="hljs-title">params</span>
      const body = <span class="hljs-keyword">node</span>.<span class="hljs-title">body</span>

      // 保存函数名
      funcIds.push(funcName.name)
      // 这是上一步推导出来的ast结构体
      const rep = expressionStatement(assignmentExpression('=', memberExpression(id('exports'), funcName),
        arrowFunctionExpression(<span class="hljs-keyword">params</span>, body)))
      // 将原来函数的ast结构体，替换成推导ast结构体
      path.replace(rep)
      // 停止遍历
      return <span class="hljs-literal">false</span>
    &#125;
  &#125;)


  recast.types.visit(ast, &#123;
    // 遍历所有的函数调用
    visitCallExpression(path)&#123;
      const <span class="hljs-keyword">node</span> <span class="hljs-title">= path</span>.<span class="hljs-keyword">node</span><span class="hljs-title">;</span>
<span class="hljs-title">      // 如果函数调用出现在函数定义中，则修改ast</span>结构
      if (funcIds.includes(<span class="hljs-keyword">node</span>.<span class="hljs-title">callee</span>.name)) &#123;
        <span class="hljs-keyword">node</span>.<span class="hljs-title">callee</span> = memberExpression(id('exports'), <span class="hljs-keyword">node</span>.<span class="hljs-title">callee</span>)
      &#125;
      // 停止遍历
      return <span class="hljs-literal">false</span>
    &#125;
  &#125;)
  // 打印修改后的ast源码
  printSource(ast)
&#125;)</code></pre></div>

<h3 id="一步到位，发一个最简单的exportific前端工具"><a href="#一步到位，发一个最简单的exportific前端工具" class="headerlink" title="一步到位，发一个最简单的exportific前端工具"></a>一步到位，发一个最简单的exportific前端工具</h3><p>上面讲了那么多，仍然只体现在理论阶段。</p>
<p>但通过简单的改写，就能通过recast制作成一个名为exportific的源码编辑工具。</p>
<p>以下代码添加作了两个小改动</p>
<ol>
<li>添加说明书–help，以及添加了–rewrite模式，可以直接覆盖文件或默认为导出*.export.js文件。</li>
<li>将之前代码最后的 printSource(ast)替换成 writeASTFile(ast,filename,rewriteMode)</li>
</ol>
<p><strong>exportific.js</strong></p>
<div class="hljs"><pre><code class="hljs javascript"><span class="hljs-meta">#!/usr/bin/env node</span>
<span class="hljs-keyword">const</span> recast = <span class="hljs-built_in">require</span>(<span class="hljs-string">"recast"</span>);
<span class="hljs-keyword">const</span> &#123;
  identifier: id,
  expressionStatement,
  memberExpression,
  assignmentExpression,
  arrowFunctionExpression
&#125; = recast.types.builders

<span class="hljs-keyword">const</span> fs = <span class="hljs-built_in">require</span>(<span class="hljs-string">'fs'</span>)
<span class="hljs-keyword">const</span> path = <span class="hljs-built_in">require</span>(<span class="hljs-string">'path'</span>)
<span class="hljs-comment">// 截取参数</span>
<span class="hljs-keyword">const</span> options = process.argv.slice(<span class="hljs-number">2</span>)

<span class="hljs-comment">//如果没有参数，或提供了-h 或--help选项，则打印帮助</span>
<span class="hljs-keyword">if</span>(options.length===<span class="hljs-number">0</span> || options.includes(<span class="hljs-string">'-h'</span>) || options.includes(<span class="hljs-string">'--help'</span>))&#123;
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`</span>
<span class="hljs-string">    采用commonjs规则，将.js文件内所有函数修改为导出形式。</span>
<span class="hljs-string"></span>
<span class="hljs-string">    选项： -r  或 --rewrite 可直接覆盖原有文件</span>
<span class="hljs-string">    `</span>)
  process.exit(<span class="hljs-number">0</span>)
&#125;

<span class="hljs-comment">// 只要有-r 或--rewrite参数，则rewriteMode为true</span>
<span class="hljs-keyword">let</span> rewriteMode = options.includes(<span class="hljs-string">'-r'</span>) || options.includes(<span class="hljs-string">'--rewrite'</span>)

<span class="hljs-comment">// 获取文件名</span>
<span class="hljs-keyword">const</span> clearFileArg = options.filter(<span class="hljs-function">(<span class="hljs-params">item</span>)=&gt;</span>&#123;
  <span class="hljs-keyword">return</span> ![<span class="hljs-string">'-r'</span>,<span class="hljs-string">'--rewrite'</span>,<span class="hljs-string">'-h'</span>,<span class="hljs-string">'--help'</span>].includes(item)
&#125;)

<span class="hljs-comment">// 只处理一个文件</span>
<span class="hljs-keyword">let</span> filename = clearFileArg[<span class="hljs-number">0</span>]

<span class="hljs-keyword">const</span> writeASTFile = <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">ast, filename, rewriteMode</span>)</span>&#123;
  <span class="hljs-keyword">const</span> newCode = recast.print(ast).code
  <span class="hljs-keyword">if</span>(!rewriteMode)&#123;
    <span class="hljs-comment">// 非覆盖模式下，将新文件写入*.export.js下</span>
    filename = filename.split(<span class="hljs-string">'.'</span>).slice(<span class="hljs-number">0</span>,<span class="hljs-number">-1</span>).concat([<span class="hljs-string">'export'</span>,<span class="hljs-string">'js'</span>]).join(<span class="hljs-string">'.'</span>)
  &#125;
  <span class="hljs-comment">// 将新代码写入文件</span>
  fs.writeFileSync(path.join(process.cwd(),filename),newCode)
&#125;


recast.run(<span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">ast, printSource</span>) </span>&#123;
  <span class="hljs-keyword">let</span> funcIds = []
  recast.types.visit(ast, &#123;
    visitFunctionDeclaration(path) &#123;
      <span class="hljs-comment">//获取遍历到的函数名、参数、块级域</span>
      <span class="hljs-keyword">const</span> node = path.node
      <span class="hljs-keyword">const</span> funcName = node.id
      <span class="hljs-keyword">const</span> params = node.params
      <span class="hljs-keyword">const</span> body = node.body

      funcIds.push(funcName.name)
      <span class="hljs-keyword">const</span> rep = expressionStatement(assignmentExpression(<span class="hljs-string">'='</span>, memberExpression(id(<span class="hljs-string">'exports'</span>), funcName),
        arrowFunctionExpression(params, body)))
      path.replace(rep)
      <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>
    &#125;
  &#125;)


  recast.types.visit(ast, &#123;
    visitCallExpression(path)&#123;
      <span class="hljs-keyword">const</span> node = path.node;
      <span class="hljs-keyword">if</span> (funcIds.includes(node.callee.name)) &#123;
        node.callee = memberExpression(id(<span class="hljs-string">'exports'</span>), node.callee)
      &#125;
      <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>
    &#125;
  &#125;)

  writeASTFile(ast,filename,rewriteMode)
&#125;)</code></pre></div>

<p>现在尝试一下</p>
<div class="hljs"><pre><code class="hljs crmsh"><span class="hljs-keyword">node</span> <span class="hljs-title">exportific</span> demo.js</code></pre></div>

<p>已经可以在当前目录下找到源码变更后的<code>demo.export.js</code>文件了。</p>
<h3 id="npm发包"><a href="#npm发包" class="headerlink" title="npm发包"></a>npm发包</h3><p>编辑一下package.json文件</p>
<div class="hljs"><pre><code class="hljs json">&#123;
  <span class="hljs-attr">"name"</span>: <span class="hljs-string">"exportific"</span>,
  <span class="hljs-attr">"version"</span>: <span class="hljs-string">"0.0.1"</span>,
  <span class="hljs-attr">"description"</span>: <span class="hljs-string">"改写源码中的函数为可exports.XXX形式"</span>,
  <span class="hljs-attr">"main"</span>: <span class="hljs-string">"exportific.js"</span>,
  <span class="hljs-attr">"bin"</span>: &#123;
    <span class="hljs-attr">"exportific"</span>: <span class="hljs-string">"./exportific.js"</span>
  &#125;,
  <span class="hljs-attr">"keywords"</span>: [],
  <span class="hljs-attr">"author"</span>: <span class="hljs-string">"wanthering"</span>,
  <span class="hljs-attr">"license"</span>: <span class="hljs-string">"ISC"</span>,
  <span class="hljs-attr">"dependencies"</span>: &#123;
    <span class="hljs-attr">"recast"</span>: <span class="hljs-string">"^0.15.3"</span>
  &#125;
&#125;</code></pre></div>

<p>注意bin选项，它的意思是将全局命令<code>exportific</code>指向当前目录下的<code>exportific.js</code></p>
<p>这时，输入<code>npm link</code> 就在本地生成了一个<code>exportific</code>命令。</p>
<p>之后，只要哪个js文件想导出来使用，就<code>exportific XXX.js</code>一下。</p>
<p>这是在本地的玩法，想和大家一起分享这个前端小工具，只需要发布npm包就行了。</p>
<p>同时，一定要注意exportific.js文件头有</p>
<div class="hljs"><pre><code class="hljs d"><span class="hljs-meta">#!/usr/bin/env node</span></code></pre></div>

<p>否则在使用时将报错。</p>
<h4 id="接下来，正式发布npm包！"><a href="#接下来，正式发布npm包！" class="headerlink" title="接下来，正式发布npm包！"></a>接下来，正式发布npm包！</h4><p>如果你已经有了npm 帐号，请使用<code>npm login</code>登录</p>
<p>如果你还没有npm帐号 <a href="https://www.npmjs.com/signup" target="_blank" rel="noopener">https://www.npmjs.com/signup</a> 非常简单就可以注册npm</p>
<p>然后，输入<br><code>npm publish</code></p>
<p>没有任何繁琐步骤，丝毫审核都没有，你就发布了一个实用的前端小工具exportific 。任何人都可以通过</p>
<div class="hljs"><pre><code class="hljs coffeescript"><span class="hljs-built_in">npm</span> i exportific -g</code></pre></div>

<p>全局安装这一个插件。</p>
<p>提示：==在试验教程时，请不要和我的包重名，修改一下发包名称。==</p>
<h3 id="结语"><a href="#结语" class="headerlink" title="结语"></a>结语</h3><p>我们对javascript再熟悉不过，但透过AST的视角，最普通的js语句，却焕发出精心动魄的美感。你可以通过它批量构建任何javascript代码！</p>
<p>童年时，这个世界充满了新奇的玩具，再普通的东西在你眼中都如同至宝。如今，计算机语言就是你手中的大玩具，一段段AST对象的拆分组装，构建出我们所生活的网络世界。</p>
<p>所以不得不说软件工程师是一个幸福的工作，你心中住的仍然是那个午后的少年，永远有无数新奇等你发现，永远有无数梦想等你构建。</p>
<h3 id="先来看一下把一个简单的函数转换成AST之后的样子。"><a href="#先来看一下把一个简单的函数转换成AST之后的样子。" class="headerlink" title="先来看一下把一个简单的函数转换成AST之后的样子。"></a>先来看一下把一个简单的函数转换成AST之后的样子。</h3><div class="hljs"><pre><code class="hljs javascript"><span class="hljs-comment">// 简单函数</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">square</span>(<span class="hljs-params">n</span>) </span>&#123;
    <span class="hljs-keyword">return</span> n * n;
&#125;

<span class="hljs-comment">// 转换后的AST</span>
&#123;
   type: <span class="hljs-string">"FunctionDeclaration"</span>,
   id: &#123;
       type: <span class="hljs-string">"Identifier"</span>,
       name: <span class="hljs-string">"square"</span>
   &#125;,
   params: [
      &#123;
           type: <span class="hljs-string">"Identifier"</span>,
           name: <span class="hljs-string">"n"</span>
      &#125;
   ],
   ...
&#125;</code></pre></div>

<p>从纯文本转换成树形结构的数据，每个条目和树中的节点一一对应。</p>
<p><strong>纯文本转AST的实现</strong></p>
<p>当下的编译器都做了纯文本转AST的事情。</p>
<p>一款编译器的编译流程是很复杂的，但我们只需要关注词法分析和语法分析，这两步是从代码生成AST的关键所在。</p>
<p><strong>第一步：词法分析，也叫扫描scanner</strong></p>
<p>它读取我们的代码，然后把它们按照预定的规则合并成一个个的标识 tokens。同时，它会移除空白符、注释等。最后，整个代码将被分割进一个 tokens 列表（或者说一维数组）。</p>
<div class="hljs"><pre><code class="hljs javascript"><span class="hljs-keyword">const</span> a = <span class="hljs-number">5</span>;
<span class="hljs-comment">// 转换成</span>
[&#123;<span class="hljs-attr">value</span>: <span class="hljs-string">'const'</span>, <span class="hljs-attr">type</span>: <span class="hljs-string">'keyword'</span>&#125;, &#123;<span class="hljs-attr">value</span>: <span class="hljs-string">'a'</span>, <span class="hljs-attr">type</span>: <span class="hljs-string">'identifier'</span>&#125;, ...]</code></pre></div>

<p>当词法分析源代码的时候，它会一个一个字母地读取代码，所以很形象地称之为扫描 - scans。当它遇到空格、操作符，或者特殊符号的时候，它会认为一个话已经完成了。</p>
<p><strong>第二步：语法分析，也称解析器</strong></p>
<p>它会将词法分析出来的数组转换成树形的形式，同时，验证语法。语法如果有错的话，抛出语法错误。</p>
<div class="hljs"><pre><code class="hljs javascript">[&#123;<span class="hljs-attr">value</span>: <span class="hljs-string">'const'</span>, <span class="hljs-attr">type</span>: <span class="hljs-string">'keyword'</span>&#125;, &#123;<span class="hljs-attr">value</span>: <span class="hljs-string">'a'</span>, <span class="hljs-attr">type</span>: <span class="hljs-string">'identifier'</span>&#125;, ...]
<span class="hljs-comment">// 语法分析后的树形形式</span>
&#123;
   type: <span class="hljs-string">"VariableDeclarator"</span>, 
   id: &#123;
       type: <span class="hljs-string">"Identifier"</span>,
       name: <span class="hljs-string">"a"</span>
   &#125;,
   ...
&#125;</code></pre></div>

<p>当生成树的时候，解析器会删除一些没必要的标识 tokens（比如：不完整的括号），因此 AST 不是 100% 与源码匹配的。</p>
<blockquote>
<p>解析器100%覆盖所有代码结构生成树叫做CST（具体语法树）。</p>
</blockquote>
<p><strong>用例：代码转换之babel</strong></p>
<p>babel 是一个 JavaScript 编译器。宏观来说，它分3个阶段运行代码：解析(parsing) — 将代码字符串转换成 AST抽象语法树，转译(transforming) — 对抽象语法树进行变换操作，生成(generation) — 根据变换后的抽象语法树生成新的代码字符串。</p>
<p>我们给 babel 一段 js 代码，它修改代码然后生成新的代码返回。它是怎么修改代码的呢？没错，它创建了 AST，遍历树，修改 tokens，最后从 AST中生成新的代码。</p>

            </article>
            <hr>
            <div>
              <div class="post-metas mb-3">
                
                
              </div>
              
                <p class="note note-warning">本博客所有文章除特别声明外，均采用 <a href="https://creativecommons.org/licenses/by-sa/4.0/deed.zh" target="_blank" rel="nofollow noopener noopener">CC BY-SA 4.0 协议</a> ，转载请注明出处！</p>
              
              
                <div class="post-prevnext row">
                  <div class="post-prev col-6">
                    
                    
                      <a href="/blog/2020/06/26/%E6%95%B0%E7%BB%84%E8%8E%B7%E5%8F%96%E7%9A%84%E6%80%A7%E8%83%BD%E6%B6%88%E8%80%97/">
                        <i class="iconfont icon-arrowleft"></i>
                        <span class="hidden-mobile">数组获取的性能消耗</span>
                        <span class="visible-mobile">上一篇</span>
                      </a>
                    
                  </div>
                  <div class="post-next col-6">
                    
                    
                      <a href="/blog/2020/06/26/ES6%20%E4%BB%A3%E7%A0%81%E8%BD%AC%E6%88%90%20ES5%20%E4%BB%A3%E7%A0%81%E5%8E%9F%E7%90%86/">
                        <span class="hidden-mobile">ES6 代码转成 ES5 代码原理</span>
                        <span class="visible-mobile">下一篇</span>
                        <i class="iconfont icon-arrowright"></i>
                      </a>
                    
                  </div>
                </div>
              
            </div>

            
              <!-- Comments -->
              <div class="comments" id="comments">
                
                

              </div>
            
          </div>
        </div>
      </div>
    </div>
    
      <div class="d-none d-lg-block col-lg-2 toc-container" id="toc-ctn">
        <div id="toc">
  <p class="toc-header"><i class="iconfont icon-list"></i>&nbsp;目录</p>
  <div id="tocbot"></div>
</div>

      </div>
    
  </div>
</div>

<!-- Custom -->


    
  </main>

  
    <a id="scroll-top-button" href="#" role="button">
      <i class="iconfont icon-arrowup" aria-hidden="true"></i>
    </a>
  

  
    <div class="modal fade" id="modalSearch" tabindex="-1" role="dialog" aria-labelledby="ModalLabel"
     aria-hidden="true">
  <div class="modal-dialog modal-dialog-scrollable modal-lg" role="document">
    <div class="modal-content">
      <div class="modal-header text-center">
        <h4 class="modal-title w-100 font-weight-bold">搜索</h4>
        <button type="button" id="local-search-close" class="close" data-dismiss="modal" aria-label="Close">
          <span aria-hidden="true">&times;</span>
        </button>
      </div>
      <div class="modal-body mx-3">
        <div class="md-form mb-5">
          <input type="text" id="local-search-input" class="form-control validate">
          <label data-error="x" data-success="v"
                 for="local-search-input">关键词</label>
        </div>
        <div class="list-group" id="local-search-result"></div>
      </div>
    </div>
  </div>
</div>
  

  

  

  <footer class="mt-5">
  <div class="text-center py-3">
    <div>
      <a href="https://hexo.io" target="_blank" rel="nofollow noopener"><span>Hexo</span></a>
      <i class="iconfont icon-love"></i>
      <a href="https://github.com/fluid-dev/hexo-theme-fluid" target="_blank" rel="nofollow noopener">
        <span>Fluid</span></a>
    </div>
    

    

    
  </div>
</footer>

<!-- SCRIPTS -->
<script  src="https://cdn.staticfile.org/jquery/3.4.1/jquery.min.js" ></script>
<script  src="https://cdn.staticfile.org/twitter-bootstrap/4.4.1/js/bootstrap.min.js" ></script>
<script  src="/blog/js/debouncer.js" ></script>
<script  src="/blog/js/main.js" ></script>

<!-- Plugins -->


  
    <script  src="/blog/js/lazyload.js" ></script>
  



  <script defer src="https://cdn.staticfile.org/clipboard.js/2.0.6/clipboard.min.js" ></script>
  <script  src="/blog/js/clipboard-use.js" ></script>



  <script defer src="https://busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js" ></script>





  <script  src="https://cdn.staticfile.org/tocbot/4.11.1/tocbot.min.js" ></script>
  <script>
    $(document).ready(function () {
      var boardCtn = $('#board-ctn');
      var boardTop = boardCtn.offset().top;

      tocbot.init({
        tocSelector: '#tocbot',
        contentSelector: 'article.markdown-body',
        headingSelector: 'h1,h2,h3,h4,h5,h6',
        linkClass: 'tocbot-link',
        activeLinkClass: 'tocbot-active-link',
        listClass: 'tocbot-list',
        isCollapsedClass: 'tocbot-is-collapsed',
        collapsibleClass: 'tocbot-is-collapsible',
        collapseDepth: 0,
        scrollSmooth: true,
        headingsOffset: -boardTop
      });
      if ($('.toc-list-item').length > 0) {
        $('#toc').css('visibility', 'visible');
      }
    });
  </script>



  <script  src="https://cdn.staticfile.org/typed.js/2.0.11/typed.min.js" ></script>
  <script>
    var typed = new Typed('#subtitle', {
      strings: [
        '  ',
        "什么是AST&nbsp;",
      ],
      cursorChar: "|",
      typeSpeed: 70,
      loop: true,
    });
    typed.stop();
    $(document).ready(function () {
      $(".typed-cursor").addClass("h2");
      typed.start();
    });
  </script>



  <script  src="https://cdn.staticfile.org/anchor-js/4.2.2/anchor.min.js" ></script>
  <script>
    anchors.options = {
      placement: "right",
      visible: "hover",
      
    };
    var el = "h1,h2,h3,h4,h5,h6".split(",");
    var res = [];
    for (item of el) {
      res.push(".markdown-body > " + item)
    }
    anchors.add(res.join(", "))
  </script>



  <script  src="/blog/js/local-search.js" ></script>
  <script>
    var path = "/blog/local-search.xml";
    var inputArea = document.querySelector("#local-search-input");
    inputArea.onclick = function () {
      searchFunc(path, 'local-search-input', 'local-search-result');
      this.onclick = null
    }
  </script>



  <script  src="https://cdn.staticfile.org/fancybox/3.5.7/jquery.fancybox.min.js" ></script>
  <link  rel="stylesheet" href="https://cdn.staticfile.org/fancybox/3.5.7/jquery.fancybox.min.css" />

  <script>
    $('#post img:not(.no-zoom img, img[no-zoom]), img[zoom]').each(
      function () {
        var element = document.createElement('a');
        $(element).attr('data-fancybox', 'images');
        $(element).attr('href', $(this).attr('src'));
        $(this).wrap(element);
      }
    );
  </script>







  
  
    <script>
      !function (e, t, a) {
        function r() {
          for (var e = 0; e < s.length; e++) s[e].alpha <= 0 ? (t.body.removeChild(s[e].el), s.splice(e, 1)) : (s[e].y--, s[e].scale += .004, s[e].alpha -= .013, s[e].el.style.cssText = "left:" + s[e].x + "px;top:" + s[e].y + "px;opacity:" + s[e].alpha + ";transform:scale(" + s[e].scale + "," + s[e].scale + ") rotate(45deg);background:" + s[e].color + ";z-index:99999");
          requestAnimationFrame(r)
        }

        function n() {
          var t = "function" == typeof e.onclick && e.onclick;
          e.onclick = function (e) {
            t && t(), o(e)
          }
        }

        function o(e) {
          var a = t.createElement("div");
          a.className = "heart", s.push({
            el: a,
            x: e.clientX - 5,
            y: e.clientY - 5,
            scale: 1,
            alpha: 1,
            color: c()
          }), t.body.appendChild(a)
        }

        function i(e) {
          var a = t.createElement("style");
          a.type = "text/css";
          try {
            a.appendChild(t.createTextNode(e))
          } catch (t) {
            a.styleSheet.cssText = e
          }
          t.getElementsByTagName("head")[0].appendChild(a)
        }

        function c() {
          return "rgb(" + ~~(255 * Math.random()) + "," + ~~(255 * Math.random()) + "," + ~~(255 * Math.random()) + ")"
        }

        var s = [];
        e.requestAnimationFrame = e.requestAnimationFrame || e.webkitRequestAnimationFrame || e.mozRequestAnimationFrame || e.oRequestAnimationFrame || e.msRequestAnimationFrame || function (e) {
          setTimeout(e, 1e3 / 60)
        }, i(".heart{width: 10px;height: 10px;position: fixed;background: #f00;transform: rotate(45deg);-webkit-transform: rotate(45deg);-moz-transform: rotate(45deg);}.heart:after,.heart:before{content: '';width: inherit;height: inherit;background: inherit;border-radius: 50%;-webkit-border-radius: 50%;-moz-border-radius: 50%;position: fixed;}.heart:after{top: -5px;}.heart:before{left: -5px;}"), n(), r()
      }(window, document);
    </script>
  













</body>
</html>
